From 23c67f8c672ee3c0a3e22afd71ac86f96592dca5 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Wed, 12 Feb 2020 20:26:29 -0500 Subject: [PATCH] New focus change handling Instead of relying on gdk's antiquated crossing events, create a new GtkCrossingData struct that contains the actual widgets, and a new event controller vfunc that expects this struct. This also saves us from making sense of X's crossing modes and details, and makes for a generally simpler api. The ::focus-in and ::focus-out signals of GtkEventControllerKey have been replaced by a single ::focus-change signal that takes GtkCrossingData as an argument. All callers have been updated. --- gtk/gtkcalendar.c | 17 +-- gtk/gtkentrycompletion.c | 14 +- gtk/gtkenums.h | 10 ++ gtk/gtkeventcontroller.c | 67 ++++++++++ gtk/gtkeventcontroller.h | 35 +++++ gtk/gtkeventcontrollerkey.c | 221 ++++++++++++-------------------- gtk/gtkeventcontrollerprivate.h | 5 + gtk/gtkfilechooserentry.c | 12 +- gtk/gtkmodelbutton.c | 20 +-- gtk/gtkpopover.c | 13 -- gtk/gtkpopovermenu.c | 12 +- gtk/gtkspinbutton.c | 14 +- gtk/gtktext.c | 74 +++++------ gtk/gtktextview.c | 108 +++++++--------- gtk/gtktreeview.c | 37 +++--- gtk/gtktreeviewcolumn.c | 8 +- gtk/gtkwidget.c | 21 +++ gtk/gtkwidgetprivate.h | 4 + gtk/gtkwindow.c | 119 ++++++++++++----- 19 files changed, 456 insertions(+), 355 deletions(-) diff --git a/gtk/gtkcalendar.c b/gtk/gtkcalendar.c index 1c97018c2d..36470a711d 100644 --- a/gtk/gtkcalendar.c +++ b/gtk/gtkcalendar.c @@ -280,9 +280,8 @@ static gboolean gtk_calendar_key_controller_key_pressed (GtkEventControllerKey * guint keycode, GdkModifierType state, GtkWidget *widget); -static void gtk_calendar_key_controller_focus (GtkEventControllerKey *controller, - GdkCrossingMode mode, - GdkNotifyType detail, +static void gtk_calendar_key_controller_focus (GtkEventController *controller, + GtkCrossingDirection direction, GtkWidget *widget); static void gtk_calendar_state_flags_changed (GtkWidget *widget, GtkStateFlags previous_state); @@ -593,10 +592,7 @@ gtk_calendar_init (GtkCalendar *calendar) g_signal_connect (controller, "key-pressed", G_CALLBACK (gtk_calendar_key_controller_key_pressed), calendar); - g_signal_connect (controller, "focus-in", - G_CALLBACK (gtk_calendar_key_controller_focus), - calendar); - g_signal_connect (controller, "focus-out", + g_signal_connect (controller, "focus-change", G_CALLBACK (gtk_calendar_key_controller_focus), calendar); gtk_widget_add_controller (GTK_WIDGET (calendar), controller); @@ -1393,10 +1389,9 @@ gtk_calendar_key_controller_key_pressed (GtkEventControllerKey *controller, } static void -gtk_calendar_key_controller_focus (GtkEventControllerKey *key, - GdkCrossingMode mode, - GdkNotifyType detail, - GtkWidget *widget) +gtk_calendar_key_controller_focus (GtkEventController *controller, + GtkCrossingDirection direction, + GtkWidget *widget) { GtkCalendar *calendar = GTK_CALENDAR (widget); GtkCalendarPrivate *priv = gtk_calendar_get_instance_private (calendar); diff --git a/gtk/gtkentrycompletion.c b/gtk/gtkentrycompletion.c index c5a95c9748..4167b7e104 100644 --- a/gtk/gtkentrycompletion.c +++ b/gtk/gtkentrycompletion.c @@ -2307,13 +2307,13 @@ accept_completion_callback (GtkEntryCompletion *completion) return FALSE; } -static gboolean -text_focus_out (GtkEntryCompletion *completion) +static void +text_focus_change (GtkEntryCompletion *completion, + GtkCrossingDirection direction) { - if (gtk_widget_get_mapped (completion->priv->popup_window)) - return FALSE; - - return accept_completion_callback (completion); + if (direction == GTK_CROSSING_OUT && + !gtk_widget_get_mapped (completion->priv->popup_window)) + accept_completion_callback (completion); } static void @@ -2349,7 +2349,7 @@ connect_completion_signals (GtkEntryCompletion *completion) controller = priv->entry_key_controller = gtk_event_controller_key_new (); g_signal_connect (controller, "key-pressed", G_CALLBACK (gtk_entry_completion_key_pressed), completion); - g_signal_connect_swapped (controller, "focus-out", G_CALLBACK (text_focus_out), completion); + g_signal_connect_swapped (controller, "focus-change", G_CALLBACK (text_focus_change), completion); gtk_widget_add_controller (GTK_WIDGET (text), controller); completion->priv->changed_id = diff --git a/gtk/gtkenums.h b/gtk/gtkenums.h index d4c3b4de2b..59eb3e17c6 100644 --- a/gtk/gtkenums.h +++ b/gtk/gtkenums.h @@ -954,6 +954,16 @@ typedef enum GTK_EVENT_SEQUENCE_DENIED } GtkEventSequenceState; +typedef enum { + GTK_CROSSING_FOCUS, + GTK_CROSSING_POINTER +} GtkCrossingType; + +typedef enum { + GTK_CROSSING_IN, + GTK_CROSSING_OUT +} GtkCrossingDirection; + /** * GtkPanDirection: * @GTK_PAN_DIRECTION_LEFT: panned towards the left diff --git a/gtk/gtkeventcontroller.c b/gtk/gtkeventcontroller.c index f67669d18d..9c77d49444 100644 --- a/gtk/gtkeventcontroller.c +++ b/gtk/gtkeventcontroller.c @@ -127,6 +127,14 @@ gtk_event_controller_handle_event_default (GtkEventController *self, return FALSE; } +static void +gtk_event_controller_handle_crossing_default (GtkEventController *self, + const GtkCrossingData *crossing, + double x, + double y) +{ +} + static void gtk_event_controller_set_property (GObject *object, guint prop_id, @@ -204,6 +212,7 @@ gtk_event_controller_class_init (GtkEventControllerClass *klass) klass->unset_widget = gtk_event_controller_unset_widget; klass->filter_event = gtk_event_controller_filter_event_default; klass->handle_event = gtk_event_controller_handle_event_default; + klass->handle_crossing = gtk_event_controller_handle_crossing_default; object_class->finalize = gtk_event_controller_finalize; object_class->set_property = gtk_event_controller_set_property; @@ -307,6 +316,34 @@ gtk_event_controller_handle_event (GtkEventController *controller, return retval; } +/** + * gtk_event_controller_handle_crossing: + * @controller: a #GtkEventController + * @crossing: a #GtkCrossingData + * @x: event position in widget coordinates + * @y: event position in widget coordinates + * + * Feeds a crossing event into @controller, so it can be interpreted + * and the controller actions triggered. + **/ +void +gtk_event_controller_handle_crossing (GtkEventController *controller, + const GtkCrossingData *crossing, + double x, + double y) +{ + GtkEventControllerClass *controller_class; + + g_return_if_fail (GTK_IS_EVENT_CONTROLLER (controller)); + g_return_if_fail (crossing != NULL); + + controller_class = GTK_EVENT_CONTROLLER_GET_CLASS (controller); + + g_object_ref (controller); + controller_class->handle_crossing (controller, crossing, x, y); + g_object_unref (controller); +} + /** * gtk_event_controller_get_widget: * @controller: a #GtkEventController @@ -451,3 +488,33 @@ gtk_event_controller_set_name (GtkEventController *controller, g_free (priv->name); priv->name = g_strdup (name); } + +static GtkCrossingData * +gtk_crossing_data_copy (GtkCrossingData *crossing) +{ + GtkCrossingData *copy; + + copy = g_new (GtkCrossingData, 1); + + copy->type = crossing->type; + copy->direction = crossing->direction; + + if (crossing->old_target) + copy->old_target = g_object_ref (crossing->old_target); + if (crossing->new_target) + copy->new_target = g_object_ref (crossing->new_target); + + return copy; +} + +static void +gtk_crossing_data_free (GtkCrossingData *crossing) +{ + g_clear_object (&crossing->old_target); + g_clear_object (&crossing->new_target); + + g_free (crossing); +} + +G_DEFINE_BOXED_TYPE (GtkCrossingData, gtk_crossing_data, + gtk_crossing_data_copy, gtk_crossing_data_free) diff --git a/gtk/gtkeventcontroller.h b/gtk/gtkeventcontroller.h index 1f058fb11b..b4694a5f7b 100644 --- a/gtk/gtkeventcontroller.h +++ b/gtk/gtkeventcontroller.h @@ -40,6 +40,36 @@ G_BEGIN_DECLS #define GTK_EVENT_CONTROLLER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_EVENT_CONTROLLER, GtkEventControllerClass)) +typedef struct _GtkCrossingData GtkCrossingData; + +/** + * GtkCrossingData: + * @type: the type of crossing event + * @direction: whether this is a focus-in or focus-out event + * @mode: the crossing mode + * @old_target: the old target + * @new_target: the new target + * + * The struct that is passed to gtk_event_controller_handle_crossing() + * and is also passed to #GtkEventControllerKey::focus-change. + * + * The @old_target and @new_target fields are set to the old or new + * focus or hover location. + */ +struct _GtkCrossingData { + GtkCrossingType type; + GtkCrossingDirection direction; + GdkCrossingMode mode; + GtkWidget *old_target; + GtkWidget *new_target; +}; + +#define GTK_TYPE_CROSSING_DATA (gtk_crossing_data_get_type ()) + +GDK_AVAILABLE_IN_ALL +GType gtk_crossing_data_get_type (void) G_GNUC_CONST; + + GDK_AVAILABLE_IN_ALL GType gtk_event_controller_get_type (void) G_GNUC_CONST; @@ -52,6 +82,11 @@ gboolean gtk_event_controller_handle_event (GtkEventController *controller double x, double y); GDK_AVAILABLE_IN_ALL +void gtk_event_controller_handle_crossing (GtkEventController *controller, + const GtkCrossingData *crossing, + double x, + double y); +GDK_AVAILABLE_IN_ALL void gtk_event_controller_reset (GtkEventController *controller); GDK_AVAILABLE_IN_ALL diff --git a/gtk/gtkeventcontrollerkey.c b/gtk/gtkeventcontrollerkey.c index 0085e636f9..47890a4aee 100644 --- a/gtk/gtkeventcontrollerkey.c +++ b/gtk/gtkeventcontrollerkey.c @@ -38,6 +38,7 @@ #include "gtkbindings.h" #include "gtkenums.h" #include "gtkmain.h" +#include "gtktypebuiltins.h" #include @@ -50,6 +51,7 @@ struct _GtkEventControllerKey GdkModifierType state; const GdkEvent *current_event; + const GtkCrossingData *current_crossing; guint is_focus : 1; guint contains_focus : 1; @@ -65,8 +67,7 @@ enum { KEY_RELEASED, MODIFIERS, IM_UPDATE, - FOCUS_IN, - FOCUS_OUT, + FOCUS_CHANGE, N_SIGNALS }; @@ -94,57 +95,6 @@ gtk_event_controller_key_finalize (GObject *object) G_OBJECT_CLASS (gtk_event_controller_key_parent_class)->finalize (object); } -static void -update_focus (GtkEventControllerKey *key, - gboolean focus_in, - GdkNotifyType detail) -{ - gboolean is_focus; - gboolean contains_focus; - - switch (detail) - { - case GDK_NOTIFY_VIRTUAL: - case GDK_NOTIFY_NONLINEAR_VIRTUAL: - is_focus = FALSE; - contains_focus = focus_in; - break; - case GDK_NOTIFY_ANCESTOR: - case GDK_NOTIFY_NONLINEAR: - is_focus = focus_in; - contains_focus = is_focus; - break; - case GDK_NOTIFY_INFERIOR: - is_focus = focus_in; - contains_focus = TRUE; - break; - case GDK_NOTIFY_UNKNOWN: - default: - g_warning ("Unknown focus change detail"); - return; - } - - g_object_freeze_notify (G_OBJECT (key)); - if (key->is_focus != is_focus) - { - key->is_focus = is_focus; - g_object_notify (G_OBJECT (key), "is-focus"); - if (key->im_context) - { - if (focus_in) - gtk_im_context_focus_in (key->im_context); - else - gtk_im_context_focus_out (key->im_context); - } - } - if (key->contains_focus != contains_focus) - { - key->contains_focus = contains_focus; - g_object_notify (G_OBJECT (key), "contains-focus"); - } - g_object_thaw_notify (G_OBJECT (key)); -} - static gboolean gtk_event_controller_key_handle_event (GtkEventController *controller, const GdkEvent *event, @@ -158,30 +108,6 @@ gtk_event_controller_key_handle_event (GtkEventController *controller, guint keyval; gboolean handled = FALSE; - if (event_type == GDK_FOCUS_CHANGE) - { - gboolean focus_in; - GdkCrossingMode mode; - GdkNotifyType detail; - - gdk_event_get_focus_in (event, &focus_in); - gdk_event_get_crossing_mode (event, &mode); - gdk_event_get_crossing_detail (event, &detail); - - update_focus (key, focus_in, detail); - - key->current_event = event; - - if (focus_in) - g_signal_emit (controller, signals[FOCUS_IN], 0, mode, detail); - else - g_signal_emit (controller, signals[FOCUS_OUT], 0, mode, detail); - - key->current_event = NULL; - - return FALSE; - } - if (event_type != GDK_KEY_PRESS && event_type != GDK_KEY_RELEASE) return FALSE; @@ -229,6 +155,64 @@ gtk_event_controller_key_handle_event (GtkEventController *controller, return handled; } +static void +update_focus (GtkEventController *controller, + const GtkCrossingData *crossing) +{ + GtkEventControllerKey *key = GTK_EVENT_CONTROLLER_KEY (controller); + GtkWidget *widget = gtk_event_controller_get_widget (controller); + gboolean is_focus = FALSE; + gboolean contains_focus = FALSE; + + if (crossing->direction == GTK_CROSSING_IN) + { + if (crossing->new_target == widget) + is_focus = TRUE; + if (crossing->new_target != NULL) + contains_focus = TRUE; + } + + g_object_freeze_notify (G_OBJECT (key)); + if (key->is_focus != is_focus) + { + key->is_focus = is_focus; + g_object_notify (G_OBJECT (key), "is-focus"); + if (key->im_context) + { + if (is_focus) + gtk_im_context_focus_in (key->im_context); + else + gtk_im_context_focus_out (key->im_context); + } + } + if (key->contains_focus != contains_focus) + { + key->contains_focus = contains_focus; + g_object_notify (G_OBJECT (key), "contains-focus"); + } + g_object_thaw_notify (G_OBJECT (key)); +} + +static void +gtk_event_controller_key_handle_crossing (GtkEventController *controller, + const GtkCrossingData *crossing, + double x, + double y) +{ + GtkEventControllerKey *key = GTK_EVENT_CONTROLLER_KEY (controller); + + if (crossing->type != GTK_CROSSING_FOCUS) + return; + + key->current_crossing = crossing; + + update_focus (controller, crossing); + + g_signal_emit (controller, signals[FOCUS_CHANGE], 0, crossing->direction); + + key->current_crossing = NULL; +} + static void gtk_event_controller_key_get_property (GObject *object, guint prop_id, @@ -261,6 +245,7 @@ gtk_event_controller_key_class_init (GtkEventControllerKeyClass *klass) object_class->finalize = gtk_event_controller_key_finalize; object_class->get_property = gtk_event_controller_key_get_property; controller_class->handle_event = gtk_event_controller_key_handle_event; + controller_class->handle_crossing = gtk_event_controller_key_handle_crossing; /** * GtkEventControllerKey:is-focus: @@ -366,7 +351,7 @@ gtk_event_controller_key_class_init (GtkEventControllerKeyClass *klass) /** * GtkEventControllerKey::im-update: - * @controller: the object which received the signal. + * @controller: the object which received the signal * * This signal is emitted whenever the input method context filters away a * keypress and prevents the @controller receiving it. See @@ -382,46 +367,27 @@ gtk_event_controller_key_class_init (GtkEventControllerKeyClass *klass) G_TYPE_NONE, 0); /** - * GtkEventControllerKey::focus-in: - * @controller: the object which received the signal. - * @mode: crossing mode indicating what caused this change - * @detail: detail indication where the focus is coming from + * GtkEventControllerKey::focus-change: + * @controller: the object which received the signal + * @direction: the direction of this crossing event * - * This signal is emitted whenever the widget controlled - * by the @controller or one of its descendants) is given - * the keyboard focus. - */ - signals[FOCUS_IN] = - g_signal_new (I_("focus-in"), - GTK_TYPE_EVENT_CONTROLLER_KEY, - G_SIGNAL_RUN_LAST, - 0, NULL, NULL, - NULL, - G_TYPE_NONE, - 2, - GDK_TYPE_CROSSING_MODE, - GDK_TYPE_NOTIFY_TYPE); - - /** - * GtkEventControllerKey::focus-out: - * @controller: the object which received the signal. - * @mode: crossing mode indicating what caused this change - * @detail: detail indication where the focus is going + * This signal is emitted whenever the focus change from or + * to a widget that is a descendant of the widget to which + * @controller is attached. * - * This signal is emitted whenever the widget controlled - * by the @controller (or one of its descendants) loses - * the keyboard focus. + * Handlers for this signal can use + * gtk_event_controller_key_get_focus_origin() and + * gtk_event_controller_key_get_focus_target() to find + * the old and new focus locations. */ - signals[FOCUS_OUT] = - g_signal_new (I_("focus-out"), + signals[FOCUS_CHANGE] = + g_signal_new (I_("focus-change"), GTK_TYPE_EVENT_CONTROLLER_KEY, G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, - G_TYPE_NONE, - 2, - GDK_TYPE_CROSSING_MODE, - GDK_TYPE_NOTIFY_TYPE); + G_TYPE_NONE, 1, + GTK_TYPE_CROSSING_DIRECTION); } static void @@ -553,29 +519,17 @@ gtk_event_controller_key_get_group (GtkEventControllerKey *controller) * Returns the widget that was holding focus before. * * This function can only be used in handlers for the - * #GtkEventControllerKey::focus-in and - * #GtkEventControllerKey::focus-out signals. + * #GtkEventControllerKey::focus-changed signal. * * Returns: (transfer none): the previous focus */ GtkWidget * gtk_event_controller_key_get_focus_origin (GtkEventControllerKey *controller) { - gboolean focus_in; - GtkWidget *origin; - g_return_val_if_fail (GTK_IS_EVENT_CONTROLLER_KEY (controller), NULL); - g_return_val_if_fail (controller->current_event != NULL, NULL); - g_return_val_if_fail (gdk_event_get_event_type (controller->current_event) == GDK_FOCUS_CHANGE, NULL); - - gdk_event_get_focus_in (controller->current_event, &focus_in); + g_return_val_if_fail (controller->current_crossing != NULL, NULL); - if (focus_in) - origin = (GtkWidget *)gdk_event_get_related_target (controller->current_event); - else - origin = (GtkWidget *)gdk_event_get_target (controller->current_event); - - return origin; + return controller->current_crossing->old_target; } /** @@ -585,26 +539,17 @@ gtk_event_controller_key_get_focus_origin (GtkEventControllerKey *controller) * Returns the widget that will be holding focus afterwards. * * This function can only be used in handlers for the - * #GtkEventControllerKey::focus-in and - * #GtkEventControllerKey::focus-out signals. + * #GtkEventControllerKey::focus-changed signal. * * Returns: (transfer none): the next focus */ GtkWidget * gtk_event_controller_key_get_focus_target (GtkEventControllerKey *controller) { - gboolean focus_in; - g_return_val_if_fail (GTK_IS_EVENT_CONTROLLER_KEY (controller), NULL); - g_return_val_if_fail (controller->current_event != NULL, NULL); - g_return_val_if_fail (gdk_event_get_event_type (controller->current_event) == GDK_FOCUS_CHANGE, NULL); + g_return_val_if_fail (controller->current_crossing != NULL, NULL); - gdk_event_get_focus_in (controller->current_event, &focus_in); - - if (focus_in) - return (GtkWidget *)gdk_event_get_target (controller->current_event); - else - return (GtkWidget *)gdk_event_get_related_target (controller->current_event); + return controller->current_crossing->new_target; } /** diff --git a/gtk/gtkeventcontrollerprivate.h b/gtk/gtkeventcontrollerprivate.h index 354956f841..aed93f311a 100644 --- a/gtk/gtkeventcontrollerprivate.h +++ b/gtk/gtkeventcontrollerprivate.h @@ -40,6 +40,11 @@ struct _GtkEventControllerClass double y); void (* reset) (GtkEventController *controller); + void (* handle_crossing) (GtkEventController *controller, + const GtkCrossingData *crossing, + double x, + double y); + /**/ /* Tells whether the event is filtered out, %TRUE makes diff --git a/gtk/gtkfilechooserentry.c b/gtk/gtkfilechooserentry.c index 2c8a73f0c8..9123bddb91 100644 --- a/gtk/gtkfilechooserentry.c +++ b/gtk/gtkfilechooserentry.c @@ -259,12 +259,12 @@ match_func (GtkEntryCompletion *compl, } static void -chooser_entry_focus_out (GtkEventControllerKey *key_controller, - GdkCrossingMode mode, - GdkNotifyType detail, - GtkFileChooserEntry *chooser_entry) +chooser_entry_focus_change (GtkEventController *controller, + GtkCrossingDirection direction, + GtkFileChooserEntry *chooser_entry) { - set_complete_on_load (chooser_entry, FALSE); + if (direction == GTK_CROSSING_OUT) + set_complete_on_load (chooser_entry, FALSE); } static void @@ -312,7 +312,7 @@ _gtk_file_chooser_entry_init (GtkFileChooserEntry *chooser_entry) G_CALLBACK (gtk_file_chooser_entry_tab_handler), chooser_entry); g_signal_connect (controller, - "focus-out", G_CALLBACK (chooser_entry_focus_out), + "focus-change", G_CALLBACK (chooser_entry_focus_change), chooser_entry); gtk_widget_add_controller (GTK_WIDGET (chooser_entry), controller); diff --git a/gtk/gtkmodelbutton.c b/gtk/gtkmodelbutton.c index 5545ed95df..7744c6cfdd 100644 --- a/gtk/gtkmodelbutton.c +++ b/gtk/gtkmodelbutton.c @@ -1359,19 +1359,21 @@ leave_cb (GtkEventController *controller, } static void -focus_in_cb (GtkEventController *controller, - GdkCrossingMode mode, - GdkNotifyType type, - gpointer data) +focus_change_cb (GtkEventController *controller, + GtkCrossingDirection direction, + gpointer data) { GtkWidget *target; GtkWidget *popover; - target = gtk_event_controller_get_widget (controller); - popover = gtk_widget_get_ancestor (target, GTK_TYPE_POPOVER_MENU); + if (direction == GTK_CROSSING_IN) + { + target = gtk_event_controller_get_widget (controller); + popover = gtk_widget_get_ancestor (target, GTK_TYPE_POPOVER_MENU); - if (popover) - gtk_popover_menu_set_active_item (GTK_POPOVER_MENU (popover), target); + if (popover) + gtk_popover_menu_set_active_item (GTK_POPOVER_MENU (popover), target); + } } static void @@ -1400,7 +1402,7 @@ gtk_model_button_init (GtkModelButton *self) gtk_widget_add_controller (GTK_WIDGET (self), controller); controller = gtk_event_controller_key_new (); - g_signal_connect (controller, "focus-in", G_CALLBACK (focus_in_cb), NULL); + g_signal_connect (controller, "focus-change", G_CALLBACK (focus_change_cb), NULL); gtk_widget_add_controller (GTK_WIDGET (self), controller); gesture = gtk_gesture_click_new (); diff --git a/gtk/gtkpopover.c b/gtk/gtkpopover.c index a5285a2766..c2e4d8ee23 100644 --- a/gtk/gtkpopover.c +++ b/gtk/gtkpopover.c @@ -563,17 +563,6 @@ gtk_popover_native_check_resize (GtkNative *native) present_popup (popover); } - -static void -gtk_popover_focus_in (GtkWidget *widget) -{ -} - -static void -gtk_popover_focus_out (GtkWidget *widget) -{ -} - static void close_menu (GtkPopover *popover) { @@ -727,8 +716,6 @@ gtk_popover_init (GtkPopover *popover) priv->has_arrow = TRUE; controller = gtk_event_controller_key_new (); - g_signal_connect_swapped (controller, "focus-in", G_CALLBACK (gtk_popover_focus_in), popover); - g_signal_connect_swapped (controller, "focus-out", G_CALLBACK (gtk_popover_focus_out), popover); g_signal_connect_swapped (controller, "key-pressed", G_CALLBACK (gtk_popover_key_pressed), popover); gtk_widget_add_controller (GTK_WIDGET (popover), controller); diff --git a/gtk/gtkpopovermenu.c b/gtk/gtkpopovermenu.c index bf56b4c5be..7e99902e6b 100644 --- a/gtk/gtkpopovermenu.c +++ b/gtk/gtkpopovermenu.c @@ -168,14 +168,14 @@ visible_submenu_changed (GObject *object, } static void -focus_out (GtkEventControllerKey *controller, - GdkCrossingMode mode, - GdkNotifyType detail, - GtkPopoverMenu *menu) +focus_change (GtkEventController *controller, + GtkCrossingDirection direction, + GtkPopoverMenu *menu) { GtkWidget *new_focus = gtk_root_get_focus (gtk_widget_get_root (GTK_WIDGET (menu))); - if (!gtk_event_controller_key_contains_focus (controller) && + if (direction == GTK_CROSSING_OUT && + !gtk_event_controller_key_contains_focus (GTK_EVENT_CONTROLLER_KEY (controller)) && new_focus != NULL) { if (menu->parent_menu && @@ -216,7 +216,7 @@ gtk_popover_menu_init (GtkPopoverMenu *popover) gtk_widget_add_css_class (GTK_WIDGET (popover), "menu"); controller = gtk_event_controller_key_new (); - g_signal_connect (controller, "focus-out", G_CALLBACK (focus_out), popover); + g_signal_connect (controller, "focus-change", G_CALLBACK (focus_change), popover); gtk_widget_add_controller (GTK_WIDGET (popover), controller); controller = gtk_event_controller_motion_new (); diff --git a/gtk/gtkspinbutton.c b/gtk/gtkspinbutton.c index bef3c0b49f..284041d168 100644 --- a/gtk/gtkspinbutton.c +++ b/gtk/gtkspinbutton.c @@ -917,14 +917,14 @@ key_controller_key_released (GtkEventControllerKey *key, } static void -key_controller_focus_out (GtkEventControllerKey *key, - GdkCrossingMode mode, - GdkNotifyType detail, - GtkSpinButton *spin_button) +key_controller_focus_change (GtkEventController *controller, + GtkCrossingDirection direction, + GtkSpinButton *spin_button) { GtkSpinButtonPrivate *priv = gtk_spin_button_get_instance_private (spin_button); - if (gtk_editable_get_editable (GTK_EDITABLE (priv->entry))) + if (direction == GTK_CROSSING_OUT && + gtk_editable_get_editable (GTK_EDITABLE (priv->entry))) gtk_spin_button_update (spin_button); } @@ -1012,8 +1012,8 @@ gtk_spin_button_init (GtkSpinButton *spin_button) controller = gtk_event_controller_key_new (); g_signal_connect (controller, "key-released", G_CALLBACK (key_controller_key_released), spin_button); - g_signal_connect (controller, "focus-out", - G_CALLBACK (key_controller_focus_out), spin_button); + g_signal_connect (controller, "focus-change", + G_CALLBACK (key_controller_focus_change), spin_button); gtk_widget_add_controller (GTK_WIDGET (spin_button), controller); } diff --git a/gtk/gtktext.c b/gtk/gtktext.c index d2396f6f17..51e3233a2a 100644 --- a/gtk/gtktext.c +++ b/gtk/gtktext.c @@ -323,8 +323,8 @@ static void gtk_text_size_allocate (GtkWidget *widget, int baseline); static void gtk_text_snapshot (GtkWidget *widget, GtkSnapshot *snapshot); -static void gtk_text_focus_in (GtkWidget *widget); -static void gtk_text_focus_out (GtkWidget *widget); +static void gtk_text_focus_change (GtkWidget *widget, + GtkCrossingDirection direction); static gboolean gtk_text_grab_focus (GtkWidget *widget); static void gtk_text_css_changed (GtkWidget *widget, GtkCssStyleChange *change); @@ -1781,10 +1781,8 @@ gtk_text_init (GtkText *self) G_CALLBACK (gtk_text_key_controller_key_pressed), self); g_signal_connect_swapped (priv->key_controller, "im-update", G_CALLBACK (gtk_text_schedule_im_reset), self); - g_signal_connect_swapped (priv->key_controller, "focus-in", - G_CALLBACK (gtk_text_focus_in), self); - g_signal_connect_swapped (priv->key_controller, "focus-out", - G_CALLBACK (gtk_text_focus_out), self); + g_signal_connect_swapped (priv->key_controller, "focus-change", + G_CALLBACK (gtk_text_focus_change), self); gtk_event_controller_key_set_im_context (GTK_EVENT_CONTROLLER_KEY (priv->key_controller), priv->im_context); gtk_widget_add_controller (GTK_WIDGET (self), priv->key_controller); @@ -3048,55 +3046,53 @@ gtk_text_key_controller_key_pressed (GtkEventControllerKey *controller, } static void -gtk_text_focus_in (GtkWidget *widget) +gtk_text_focus_change (GtkWidget *widget, + GtkCrossingDirection direction) { GtkText *self = GTK_TEXT (widget); GtkTextPrivate *priv = gtk_text_get_instance_private (self); GdkKeymap *keymap; - gtk_widget_queue_draw (widget); - - keymap = gdk_display_get_keymap (gtk_widget_get_display (widget)); - - if (priv->editable) + if (direction == GTK_CROSSING_IN) { - gtk_text_schedule_im_reset (self); - gtk_im_context_focus_in (priv->im_context); - } + gtk_widget_queue_draw (widget); - g_signal_connect (keymap, "direction-changed", - G_CALLBACK (keymap_direction_changed), self); + keymap = gdk_display_get_keymap (gtk_widget_get_display (widget)); - gtk_text_reset_blink_time (self); - gtk_text_check_cursor_blink (self); -} + if (priv->editable) + { + gtk_text_schedule_im_reset (self); + gtk_im_context_focus_in (priv->im_context); + } -static void -gtk_text_focus_out (GtkWidget *widget) -{ - GtkText *self = GTK_TEXT (widget); - GtkTextPrivate *priv = gtk_text_get_instance_private (self); - GdkKeymap *keymap; + g_signal_connect (keymap, "direction-changed", + G_CALLBACK (keymap_direction_changed), self); - gtk_text_selection_bubble_popup_unset (self); + gtk_text_reset_blink_time (self); + gtk_text_check_cursor_blink (self); + } + else + { + gtk_text_selection_bubble_popup_unset (self); - if (priv->text_handle) - _gtk_text_handle_set_mode (priv->text_handle, - GTK_TEXT_HANDLE_MODE_NONE); + if (priv->text_handle) + _gtk_text_handle_set_mode (priv->text_handle, + GTK_TEXT_HANDLE_MODE_NONE); - gtk_widget_queue_draw (widget); + gtk_widget_queue_draw (widget); - keymap = gdk_display_get_keymap (gtk_widget_get_display (widget)); + keymap = gdk_display_get_keymap (gtk_widget_get_display (widget)); - if (priv->editable) - { - gtk_text_schedule_im_reset (self); - gtk_im_context_focus_out (priv->im_context); - } + if (priv->editable) + { + gtk_text_schedule_im_reset (self); + gtk_im_context_focus_out (priv->im_context); + } - gtk_text_check_cursor_blink (self); + gtk_text_check_cursor_blink (self); - g_signal_handlers_disconnect_by_func (keymap, keymap_direction_changed, self); + g_signal_handlers_disconnect_by_func (keymap, keymap_direction_changed, self); + } } static gboolean diff --git a/gtk/gtktextview.c b/gtk/gtktextview.c index 04aba8b4c8..f55a784741 100644 --- a/gtk/gtktextview.c +++ b/gtk/gtktextview.c @@ -404,8 +404,8 @@ static gboolean gtk_text_view_key_controller_key_pressed (GtkEventControllerKey static void gtk_text_view_key_controller_im_update (GtkEventControllerKey *controller, GtkTextView *text_view); -static void gtk_text_view_focus_in (GtkWidget *widget); -static void gtk_text_view_focus_out (GtkWidget *widget); +static void gtk_text_view_focus_change (GtkWidget *widget, + GtkCrossingDirection direction); static void gtk_text_view_motion (GtkEventController *controller, double x, double y, @@ -1695,11 +1695,8 @@ gtk_text_view_init (GtkTextView *text_view) g_signal_connect (priv->key_controller, "im-update", G_CALLBACK (gtk_text_view_key_controller_im_update), widget); - g_signal_connect_swapped (priv->key_controller, "focus-in", - G_CALLBACK (gtk_text_view_focus_in), - widget); - g_signal_connect_swapped (priv->key_controller, "focus-out", - G_CALLBACK (gtk_text_view_focus_out), + g_signal_connect_swapped (priv->key_controller, "focus-change", + G_CALLBACK (gtk_text_view_focus_change), widget); gtk_event_controller_key_set_im_context (GTK_EVENT_CONTROLLER_KEY (priv->key_controller), priv->im_context); @@ -5318,72 +5315,65 @@ keymap_direction_changed (GdkKeymap *keymap, } static void -gtk_text_view_focus_in (GtkWidget *widget) +gtk_text_view_focus_change (GtkWidget *widget, + GtkCrossingDirection direction) { - GtkTextView *text_view; - GtkTextViewPrivate *priv; - - text_view = GTK_TEXT_VIEW (widget); - priv = text_view->priv; - - gtk_widget_queue_draw (widget); - - DV(g_print (G_STRLOC": focus_in\n")); - - gtk_text_view_reset_blink_time (text_view); + GtkTextView *text_view = GTK_TEXT_VIEW (widget); + GtkTextViewPrivate *priv = text_view->priv; - if (cursor_visible (text_view) && priv->layout) + if (direction == GTK_CROSSING_IN) { - gtk_text_layout_set_cursor_visible (priv->layout, TRUE); - gtk_text_view_check_cursor_blink (text_view); - } + gtk_widget_queue_draw (widget); - g_signal_connect (gdk_display_get_keymap (gtk_widget_get_display (widget)), - "direction-changed", - G_CALLBACK (keymap_direction_changed), text_view); - gtk_text_view_check_keymap_direction (text_view); + DV(g_print (G_STRLOC": focus_in\n")); - if (priv->editable) - { - priv->need_im_reset = TRUE; - gtk_im_context_focus_in (priv->im_context); - } -} + gtk_text_view_reset_blink_time (text_view); -static void -gtk_text_view_focus_out (GtkWidget *widget) -{ - GtkTextView *text_view; - GtkTextViewPrivate *priv; + if (cursor_visible (text_view) && priv->layout) + { + gtk_text_layout_set_cursor_visible (priv->layout, TRUE); + gtk_text_view_check_cursor_blink (text_view); + } - text_view = GTK_TEXT_VIEW (widget); - priv = text_view->priv; + g_signal_connect (gdk_display_get_keymap (gtk_widget_get_display (widget)), + "direction-changed", + G_CALLBACK (keymap_direction_changed), text_view); + gtk_text_view_check_keymap_direction (text_view); - gtk_text_view_end_selection_drag (text_view); + if (priv->editable) + { + priv->need_im_reset = TRUE; + gtk_im_context_focus_in (priv->im_context); + } + } + else + { + gtk_text_view_end_selection_drag (text_view); - gtk_widget_queue_draw (widget); + gtk_widget_queue_draw (widget); - DV(g_print (G_STRLOC": focus_out\n")); + DV(g_print (G_STRLOC": focus_out\n")); - if (cursor_visible (text_view) && priv->layout) - { - gtk_text_view_check_cursor_blink (text_view); - gtk_text_layout_set_cursor_visible (priv->layout, FALSE); - } + if (cursor_visible (text_view) && priv->layout) + { + gtk_text_view_check_cursor_blink (text_view); + gtk_text_layout_set_cursor_visible (priv->layout, FALSE); + } - g_signal_handlers_disconnect_by_func (gdk_display_get_keymap (gtk_widget_get_display (widget)), - keymap_direction_changed, - text_view); - gtk_text_view_selection_bubble_popup_unset (text_view); + g_signal_handlers_disconnect_by_func (gdk_display_get_keymap (gtk_widget_get_display (widget)), + keymap_direction_changed, + text_view); + gtk_text_view_selection_bubble_popup_unset (text_view); - if (priv->text_handle) - _gtk_text_handle_set_mode (priv->text_handle, - GTK_TEXT_HANDLE_MODE_NONE); + if (priv->text_handle) + _gtk_text_handle_set_mode (priv->text_handle, + GTK_TEXT_HANDLE_MODE_NONE); - if (priv->editable) - { - priv->need_im_reset = TRUE; - gtk_im_context_focus_out (priv->im_context); + if (priv->editable) + { + priv->need_im_reset = TRUE; + gtk_im_context_focus_out (priv->im_context); + } } } diff --git a/gtk/gtktreeview.c b/gtk/gtktreeview.c index cb3f3eb685..6e0574c4d7 100644 --- a/gtk/gtktreeview.c +++ b/gtk/gtktreeview.c @@ -667,10 +667,9 @@ static void gtk_tree_view_key_controller_key_released (GtkEventControllerKey guint keycode, GdkModifierType state, GtkTreeView *tree_view); -static void gtk_tree_view_key_controller_focus_out (GtkEventControllerKey *key, - GdkCrossingMode mode, - GdkNotifyType detail, - GtkTreeView *tree_view); +static void gtk_tree_view_key_controller_focus_change (GtkEventController *key, + GtkCrossingDirection direction, + GtkTreeView *tree_view); static gint gtk_tree_view_focus (GtkWidget *widget, GtkDirectionType direction); @@ -1839,8 +1838,8 @@ gtk_tree_view_init (GtkTreeView *tree_view) G_CALLBACK (gtk_tree_view_key_controller_key_pressed), tree_view); g_signal_connect (controller, "key-released", G_CALLBACK (gtk_tree_view_key_controller_key_released), tree_view); - g_signal_connect (controller, "focus-out", - G_CALLBACK (gtk_tree_view_key_controller_focus_out), tree_view); + g_signal_connect (controller, "focus-change", + G_CALLBACK (gtk_tree_view_key_controller_focus_change), tree_view); gtk_widget_add_controller (GTK_WIDGET (tree_view), controller); } @@ -5546,23 +5545,19 @@ gtk_tree_view_motion_controller_leave (GtkEventControllerMotion *controller, } static void -gtk_tree_view_key_controller_focus_out (GtkEventControllerKey *key, - GdkCrossingMode mode, - GdkNotifyType detail, - GtkTreeView *tree_view) +gtk_tree_view_key_controller_focus_change (GtkEventController *key, + GtkCrossingDirection direction, + GtkTreeView *tree_view) { - gboolean is_focus, contains_focus; - - gtk_widget_queue_draw (GTK_WIDGET (tree_view)); - - g_object_get (key, - "is-focus", &is_focus, - "contains-focus", &contains_focus, - NULL); + if (direction == GTK_CROSSING_OUT) + { + gtk_widget_queue_draw (GTK_WIDGET (tree_view)); - if (tree_view->search_popover && !gtk_event_controller_key_contains_focus (key)) - gtk_tree_view_search_popover_hide (tree_view->search_popover, tree_view, - gtk_get_current_event_device ()); + if (tree_view->search_popover && + !gtk_event_controller_key_contains_focus (GTK_EVENT_CONTROLLER_KEY (key))) + gtk_tree_view_search_popover_hide (tree_view->search_popover, tree_view, + gtk_get_current_event_device ()); + } } /* Incremental Reflow diff --git a/gtk/gtktreeviewcolumn.c b/gtk/gtktreeviewcolumn.c index aa39d9b9ba..790e611036 100644 --- a/gtk/gtktreeviewcolumn.c +++ b/gtk/gtktreeviewcolumn.c @@ -831,11 +831,11 @@ gtk_tree_view_column_cell_layout_get_area (GtkCellLayout *cell_layout) static void focus_in (GtkEventControllerKey *controller, - GdkCrossingMode mode, - GdkNotifyType detail, + GtkCrossingDirection direction, GtkTreeViewColumn *column) { - _gtk_tree_view_set_focus_column (GTK_TREE_VIEW (column->priv->tree_view), column); + if (direction == GTK_CROSSING_IN) + _gtk_tree_view_set_focus_column (GTK_TREE_VIEW (column->priv->tree_view), column); } /* Button handling code @@ -866,7 +866,7 @@ gtk_tree_view_column_create_button (GtkTreeViewColumn *tree_column) gtk_widget_add_controller (priv->button, controller); controller = gtk_event_controller_key_new (); - g_signal_connect (controller, "focus-in", G_CALLBACK (focus_in), tree_column); + g_signal_connect (controller, "focus-change", G_CALLBACK (focus_in), tree_column); gtk_widget_add_controller (priv->button, controller); priv->frame = gtk_frame_new (NULL); diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c index 0e937bd10e..57d536d510 100644 --- a/gtk/gtkwidget.c +++ b/gtk/gtkwidget.c @@ -4852,6 +4852,27 @@ gtk_widget_run_controllers (GtkWidget *widget, return handled; } +void +gtk_widget_handle_crossing (GtkWidget *widget, + const GtkCrossingData *crossing, + double x, + double y) +{ + GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget); + GList *l; + + g_object_ref (widget); + + for (l = priv->event_controllers; l; l = l->next) + { + GtkEventController *controller = l->data; + + gtk_event_controller_handle_crossing (controller, crossing, x, y); + } + + g_object_unref (widget); +} + static gboolean translate_event_coordinates (GdkEvent *event, double *x, diff --git a/gtk/gtkwidgetprivate.h b/gtk/gtkwidgetprivate.h index 87d0eba9c0..bc24a9c728 100644 --- a/gtk/gtkwidgetprivate.h +++ b/gtk/gtkwidgetprivate.h @@ -347,6 +347,10 @@ gboolean gtk_widget_run_controllers (GtkWidget double x, double y, GtkPropagationPhase phase); +void gtk_widget_handle_crossing (GtkWidget *widget, + const GtkCrossingData *crossing, + double x, + double y); guint gtk_widget_add_surface_transform_changed_callback (GtkWidget *widget, diff --git a/gtk/gtkwindow.c b/gtk/gtkwindow.c index a8de122391..4302654304 100644 --- a/gtk/gtkwindow.c +++ b/gtk/gtkwindow.c @@ -394,8 +394,8 @@ static void gtk_window_size_allocate (GtkWidget *widget, int height, int baseline); static gboolean gtk_window_close_request (GtkWindow *window); -static void gtk_window_focus_in (GtkWidget *widget); -static void gtk_window_focus_out (GtkWidget *widget); +static void gtk_window_focus_change (GtkWidget *widget, + GtkCrossingDirection direction); static gboolean gtk_window_key_pressed (GtkWidget *widget, guint keyval, guint keycode, @@ -1844,10 +1844,8 @@ gtk_window_init (GtkWindow *window) priv->key_controller = gtk_event_controller_key_new (); gtk_event_controller_set_propagation_phase (priv->key_controller, GTK_PHASE_CAPTURE); - g_signal_connect_swapped (priv->key_controller, "focus-in", - G_CALLBACK (gtk_window_focus_in), window); - g_signal_connect_swapped (priv->key_controller, "focus-out", - G_CALLBACK (gtk_window_focus_out), window); + g_signal_connect_swapped (priv->key_controller, "focus-change", + G_CALLBACK (gtk_window_focus_change), window); g_signal_connect_swapped (priv->key_controller, "key-pressed", G_CALLBACK (gtk_window_key_pressed), window); g_signal_connect_swapped (priv->key_controller, "key-released", @@ -6121,33 +6119,33 @@ gtk_window_has_mnemonic_modifier_pressed (GtkWindow *window) } static void -gtk_window_focus_in (GtkWidget *widget) +gtk_window_focus_change (GtkWidget *widget, + GtkCrossingDirection direction) { GtkWindow *window = GTK_WINDOW (widget); - /* It appears spurious focus in events can occur when - * the window is hidden. So we'll just check to see if - * the window is visible before actually handling the - * event - */ - if (gtk_widget_get_visible (widget)) + if (direction == GTK_CROSSING_IN) { - _gtk_window_set_is_active (window, TRUE); + /* It appears spurious focus in events can occur when + * the window is hidden. So we'll just check to see if + * the window is visible before actually handling the + * event + */ + if (gtk_widget_get_visible (widget)) + { + _gtk_window_set_is_active (window, TRUE); - if (gtk_window_has_mnemonic_modifier_pressed (window)) - _gtk_window_schedule_mnemonics_visible (window); + if (gtk_window_has_mnemonic_modifier_pressed (window)) + _gtk_window_schedule_mnemonics_visible (window); + } } -} - -static void -gtk_window_focus_out (GtkWidget *widget) -{ - GtkWindow *window = GTK_WINDOW (widget); - - _gtk_window_set_is_active (window, FALSE); + else + { + _gtk_window_set_is_active (window, FALSE); - /* set the mnemonic-visible property to false */ - gtk_window_set_mnemonics_visible (window, FALSE); + /* set the mnemonic-visible property to false */ + gtk_window_set_mnemonics_visible (window, FALSE); + } } static void @@ -6347,6 +6345,61 @@ gtk_window_move_focus (GtkWidget *widget, gtk_window_set_focus (GTK_WINDOW (widget), NULL); } +static void +synthesize_focus_change_events (GtkWindow *window, + GtkWidget *old_focus, + GtkWidget *new_focus) +{ + GtkCrossingData crossing; + GtkWidget *widget, *focus_child; + GList *list, *l; + GtkStateFlags flags; + + flags = GTK_STATE_FLAG_FOCUSED; + if (gtk_window_get_focus_visible (GTK_WINDOW (window))) + flags |= GTK_STATE_FLAG_FOCUS_VISIBLE; + + crossing.type = GTK_CROSSING_FOCUS; + crossing.mode = GDK_CROSSING_NORMAL; + crossing.old_target = old_focus; + crossing.new_target = new_focus; + + crossing.direction = GTK_CROSSING_OUT; + + widget = old_focus; + while (widget) + { + gtk_widget_handle_crossing (widget, &crossing, 0, 0); + gtk_widget_unset_state_flags (widget, flags); + gtk_widget_set_focus_child (widget, NULL); + widget = gtk_widget_get_parent (widget); + } + + list = NULL; + widget = new_focus; + while (widget) + { + list = g_list_prepend (list, widget); + widget = gtk_widget_get_parent (widget); + } + + crossing.direction = GTK_CROSSING_IN; + + for (l = list; l; l = l->next) + { + widget = l->data; + if (l->next) + focus_child = l->next->data; + else + focus_child = NULL; + gtk_widget_handle_crossing (widget, &crossing, 0, 0); + gtk_widget_set_state_flags (widget, flags, FALSE); + gtk_widget_set_focus_child (widget, focus_child); + } + + g_list_free (list); +} + /** * gtk_window_set_focus: * @window: a #GtkWindow @@ -6365,9 +6418,6 @@ gtk_window_set_focus (GtkWindow *window, { GtkWindowPrivate *priv = gtk_window_get_instance_private (window); GtkWidget *old_focus = NULL; - GdkSeat *seat; - GdkDevice *device; - GdkEvent *event; g_return_if_fail (GTK_IS_WINDOW (window)); @@ -6381,14 +6431,13 @@ gtk_window_set_focus (GtkWindow *window, old_focus = g_object_ref (priv->focus_widget); g_set_object (&priv->focus_widget, NULL); - seat = gdk_display_get_default_seat (gtk_widget_get_display (GTK_WIDGET (window))); - device = gdk_seat_get_keyboard (seat); + if (old_focus) + gtk_widget_set_has_focus (old_focus, FALSE); - event = gdk_event_focus_new (priv->surface, device, device, TRUE); + synthesize_focus_change_events (window, old_focus, focus); - gtk_synthesize_crossing_events (GTK_ROOT (window), old_focus, focus, event, GDK_CROSSING_NORMAL); - - gdk_event_unref (event); + if (focus) + gtk_widget_set_has_focus (focus, TRUE); g_set_object (&priv->focus_widget, focus); -- 2.30.2